<!DOCTYPE html>
<!--
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->

<html>
<!--
  Copyright 2008 Google Inc. All Rights Reserved.

-->
<head>
<title>Closure Unit Tests - goog.net.ChannelRequest</title>
<script src="../base.js"></script>
<script>
goog.require('goog.net.BrowserChannel');
goog.require('goog.net.ChannelDebug');
goog.require('goog.net.ChannelRequest');
goog.require('goog.testing.jsunit');
goog.require('goog.testing.MockClock');
goog.require('goog.testing.net.XhrIo');
</script>
</head>
<body>
<script>

var mockClock;

/**
 * Delay between a network failure and the next network request.
 */
var RETRY_TIME = 1000;

/**
 * Time to wait for a network request to time out, before aborting.
 */
var WATCHDOG_TIME = 2000;

/**
 * A really long time - used to make sure no more timeouts will fire.
 */
var ALL_DAY_MS = 1000 * 60 * 60 * 24;


function setUp() {
  mockClock = new goog.testing.MockClock();
  mockClock.install();
}


function tearDown() {
  mockClock.uninstall();
}


/**
 * Constructs a duck-type BrowserChannel that tracks the completed requests.
  * @constructor
 */
function MockBrowserChannel() {
  this.isClosed = function() {
    return false;
  };
  this.isActive = function() {
    return true;
  };
  this.completedRequests = [];
  this.onRequestComplete = function(request) {
    this.completedRequests.push(request);
  };
  this.onRequestData = function(request, data) {};
}


/**
 * Creates a real ChannelRequest object, with some modifications for
 * testability:
 * <ul>
 * <li>The BrowserChannel is a MockBrowserChannel.
 * <li>getRetryTime_() always returns RETRY_TIME.
 * <li>createXhrIo_() returns the xhrIo that is passed in.
 * <li>The new watchdogTimeoutCallCount property tracks onWatchDogTimout_()
 *     calls.
 * <li>The new retryCallCount property tracks retry_() calls.
 * <li>The "retry timeout" is set to WATCHDOG_TIME.
 * </ul>
 * @param {goog.testing.net.XhrIo} xhrIo  A test XhrIo.
 * @return {goog.net.ChannelRequest} A slightly modified ChannelRequest
 */
function createChannelRequest(xhrIo) {
  // Install mock browser channel and no-op debug logger.
  var req = new goog.net.ChannelRequest(
      new MockBrowserChannel(),
      new goog.net.ChannelDebug());

  // Provide a predictable retry time for testing.
  req.getRetryTime_ = function() {
    return RETRY_TIME;
  };

  // Install test XhrIo.
  req.createXhrIo_ = function() {
    return xhrIo;
  };

  // Install watchdogTimeoutCallCount.
  req.watchdogTimeoutCallCount = 0;
  req.originalOnWatchDogTimout = req.onWatchDogTimout_;
  req.onWatchDogTimout_ = function() {
    this.watchdogTimeoutCallCount++;
    return this.originalOnWatchDogTimout();
  };

  // Install retryCallCount.
  req.retryCallCount = 0;
  req.originalRetry = req.retry_;
  req.retry_ = function() {
    this.retryCallCount++;
    return this.originalRetry();
  };

  req.setRetryTimeout(WATCHDOG_TIME);
  
  return req;
}


/**
 * Creates a test XhrIo, with the abort() method defined.
 * @return {goog.testing.net.XhrIo} A test XhrIo.
 */
function createXhrIo() {
  var xhrIo = new goog.testing.net.XhrIo();
  xhrIo.abort = xhrIo.abort || function() {
    this.active_ = false;
  };
  return xhrIo;
}


/**
 * Make sure setMaxRetries sets a field.
 */
function testSetMaxRetriesSetsSomething() {
  var req = createChannelRequest();
  assertEquals(0, req.maxRetries_);
  req.setMaxRetries(1);
  assertEquals(1, req.maxRetries_);
}


/**
 * Make sure that dropping maxRetries below the retryCount reports an error,
 * and prevents another request from firing.
 */
function testSetMaxRetriesBelowRetryCountWhileWaitingForRetry() {
  var xhrIo = createXhrIo();
  var req = createChannelRequest(xhrIo);

  req.setMaxRetries(2);
  req.xmlHttpPost(new goog.Uri('some_uri'), 'some_postdata', true);
  assertEquals(0, req.watchdogTimeoutCallCount);
  assertEquals(0, req.retryCount_);
  assertEquals(0, req.retryCallCount);
  assertEquals(0, req.channel_.completedRequests.length);

  // Watchdog timeout.
  mockClock.tick(WATCHDOG_TIME);
  assertEquals(1, req.watchdogTimeoutCallCount);
  assertEquals(1, req.retryCount_);
  assertEquals(0, req.retryCallCount);
  assertEquals(0, req.channel_.completedRequests.length);

  // Almost finish the between-retry timeout.
  mockClock.tick(RETRY_TIME - 1);
  assertEquals(1, req.watchdogTimeoutCallCount);
  assertEquals(1, req.retryCount_);
  assertEquals(0, req.retryCallCount);
  assertEquals(0, req.channel_.completedRequests.length);

  // Setting max retries to 0 should fail the req.
  req.setMaxRetries(0);
  assertEquals(1, req.watchdogTimeoutCallCount);
  assertEquals(1, req.retryCount_);
  assertEquals(0, req.retryCallCount);
  assertEquals(1, req.channel_.completedRequests.length);
  assertFalse(req.getSuccess());

  // Make sure no more retry timers are firing.
  mockClock.tick(ALL_DAY_MS);
  assertEquals(1, req.watchdogTimeoutCallCount);
  assertEquals(1, req.retryCount_);
  assertEquals(0, req.retryCallCount);
  assertEquals(1, req.channel_.completedRequests.length);
}


/**
 * Make sure that dropping maxRetries below the retryCount reports an error,
 * and prevents another request from firing.
 */
function testSetMaxRetriesBelowRetryCountWhileRetryXhrIsInFlight() {
  var xhrIo = createXhrIo();
  var req = createChannelRequest(xhrIo);

  req.setMaxRetries(2);
  req.xmlHttpPost(new goog.Uri('some_uri'), 'some_postdata', true);
  assertEquals(0, req.watchdogTimeoutCallCount);
  assertEquals(0, req.retryCount_);
  assertEquals(0, req.retryCallCount);
  assertEquals(0, req.channel_.completedRequests.length);

  // Watchdog timeout.
  mockClock.tick(WATCHDOG_TIME);
  assertEquals(1, req.watchdogTimeoutCallCount);
  assertEquals(1, req.retryCount_);
  assertEquals(0, req.retryCallCount);
  assertEquals(0, req.channel_.completedRequests.length);

  // Wait for the between-retry timeout.
  mockClock.tick(RETRY_TIME);
  assertEquals(1, req.watchdogTimeoutCallCount);
  assertEquals(1, req.retryCount_);
  assertEquals(1, req.retryCallCount);
  assertEquals(0, req.channel_.completedRequests.length);

  // Simulate a second watchdog timeout.
  mockClock.tick(WATCHDOG_TIME);
  assertEquals(2, req.watchdogTimeoutCallCount);
  assertEquals(2, req.retryCount_);
  assertEquals(1, req.retryCallCount);
  assertEquals(0, req.channel_.completedRequests.length);

  // Wait for another between-retry timeout.
  mockClock.tick(RETRY_TIME);
  // Now the second XHR req is in flight.
  assertEquals(2, req.watchdogTimeoutCallCount);
  assertEquals(2, req.retryCount_);
  assertEquals(2, req.retryCallCount);
  assertEquals(0, req.channel_.completedRequests.length);

  // Almost watchdog timeout, but not quite.
  mockClock.tick(WATCHDOG_TIME - 1);
  assertEquals(2, req.watchdogTimeoutCallCount);
  assertEquals(2, req.retryCount_);
  assertEquals(2, req.retryCallCount);
  assertEquals(0, req.channel_.completedRequests.length);

  // Set max retries below actual retries, killing the request
  req.setMaxRetries(0);
  assertEquals(2, req.watchdogTimeoutCallCount);
  assertEquals(2, req.retryCount_);
  assertEquals(2, req.retryCallCount);
  assertEquals(1, req.channel_.completedRequests.length);
  assertFalse(req.getSuccess());

  // Make sure no more retry timers are firing.
  mockClock.tick(ALL_DAY_MS);
  assertEquals(2, req.watchdogTimeoutCallCount);
  assertEquals(2, req.retryCount_);
  assertEquals(2, req.retryCallCount);
  assertEquals(1, req.channel_.completedRequests.length);
}


/**
 * Makes sure that setting maxRetries to actual retries doesn't cause a failure.
 */
function testSetMaxRetriesAtRetryCount() {
  var xhrIo = createXhrIo();
  var req = createChannelRequest(xhrIo);

  req.setMaxRetries(1);
  req.xmlHttpPost(new goog.Uri('some_uri'), 'some_postdata', true);

  // Watchdog timeout.
  mockClock.tick(WATCHDOG_TIME);
  // Start retry #1
  assertEquals(1, req.watchdogTimeoutCallCount);
  assertEquals(1, req.retryCount_);
  assertEquals(0, req.retryCallCount);
  assertEquals(0, req.channel_.completedRequests.length);

  // Wait for the between-retry timeout.
  mockClock.tick(RETRY_TIME);
  // Actually retry XHR.
  assertEquals(1, req.watchdogTimeoutCallCount);
  assertEquals(1, req.retryCount_);
  assertEquals(1, req.retryCallCount);
  assertEquals(0, req.channel_.completedRequests.length);

  // Set max retries to be the same.
  req.setMaxRetries(1);
  assertEquals(1, req.watchdogTimeoutCallCount);
  assertEquals(1, req.retryCount_);
  assertEquals(1, req.retryCallCount);
  assertEquals(0, req.channel_.completedRequests.length);

  // Watchdog timeout.  Request has failed.
  mockClock.tick(WATCHDOG_TIME);
  assertEquals(2, req.watchdogTimeoutCallCount);
  assertEquals(1, req.retryCount_);
  assertEquals(1, req.retryCallCount);
  assertEquals(1, req.channel_.completedRequests.length);
  assertFalse(req.getSuccess());

  // Make sure no more retry timers are firing.
  mockClock.tick(ALL_DAY_MS);
  assertEquals(2, req.watchdogTimeoutCallCount);
  assertEquals(1, req.retryCount_);
  assertEquals(1, req.retryCallCount);
  assertEquals(1, req.channel_.completedRequests.length);
}

</script>
</body>
</html>
